/*
 * Copyright 2008 Mark Davidson.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */

package org.doofer

import com.documentum.fc.client.*
import com.documentum.fc.common.*

/**
 * Provides convenience methods for running DQL queries.
 *
 * @author Mark Davidson
 */
class QueryUtil {

    /**
     * Constructor
     */
    QueryUtil() {}

    /**
     * Executes a DQL query and optionally allows the results to be
     * iterated using a closure (the IDfCollection is passed to the
     * closure as a parameter). If the queryType is not specified then
     * the queryType will be guessed at by checking the DQL. The named
     * parameters are:
     *
     * <ul>
     * <li>session - the repository session</li>
     * <li>query - the IDfQuery instance</li>
     * <li>dql - the DQL command string</li>
     * <li>queryType - the type of query (see IDfQuery for valid integer values)</li>
     * <li>closure - the closure</li>
     * </ul>
     *
     * @param params The named parameters. See description for more info.
     * @throws DfException
     */
    void execute(Map params, Closure closure = null) {
        def collection
        def session = params.session
        def query = params.query

        if(!query) {
            query = new DfQuery()
            query.DQL = params?.dql
        }

        def queryType = params?.queryType
        if(!queryType) queryType = guessQueryType(query.DQL)

        try {
            collection = query.execute(session, queryType)

            if(closure) {
                while(collection.next()) {
                    closure(collection)
                }
            }
        } finally {
            collection?.close()
        }
    }

    /**
     * Runs a query, returning the results as a list of Map instances.
     * The named parameters are:
     * <ul>
     * <li>session - the repository session</li>
     * <li>query - the IDfQuery instance</li>
     * <li>dql - the DQL command string</li>
     * <li>queryType - the type of query (see IDfQuery for valid integer values)</li>
     * </ul>
     *
     * @param params The named parameters. See description for further info.
     */
    def toList(Map params, Closure closure = null) {
        def au = new AttribUtil()
        def results = []

        execute(params) { row ->
            def attribs = au.readAttribs(object: row)
            if(closure) closure(attribs)
            results << attribs
        }

        results
    }

    /**
     * Guesses the queryType for a specified DQL statement
     *
     * @param dql The DQL query
     * @return The query type
     */
    def guessQueryType(dql) {
        def queryType

        if(dql =~ /(?i)^(delete|execute|insert|update)/) {
            queryType = IDfQuery.DF_EXEC_QUERY
        } else {
            queryType = IDfQuery.DF_READ_QUERY
        }

        queryType
    }

    /**
     * Walks through a folder structure, firing a closure on each
     * object it encounters. The closure parameter is  map of
     * the attributes object_name, r_object_id, r_object_type,
     * acl_name, acl_domain, r_content_size, a_content_type, r_link_cnt,
     * r_is_virtual_doc and path.
     *
     * <ul>
     * <li>session - the session</li>
     * <li>path - the path to walk from (optional)/li>
     * <li>recursive - whether to drilldown through the folders (optional)</li>
     * <li>walkByWidth - whether to walk by width (optional - defaults to walk by depth)/li>
     * <li>queryType - the query type (optional)</li>
     * </ul>
     *
     * @param params The named parameters. See description for further info.
     */
    def walk(Map params, Closure closure) {
        def session = params.session
        def path = params.path ?: '/'
        def recursive = params.recursive
        def walkByWidth = params.walkByWidth
        def queryType = params.queryType
        def dql = null

        if(path == '/') {
            dql =
            """
            select object_name, r_object_id, r_object_type, acl_name, acl_domain, r_content_size, a_content_type, r_link_cnt, r_is_virtual_doc
            from dm_cabinet
            order by object_name, r_object_id, r_object_type, acl_name, acl_domain, r_content_size, a_content_type, r_link_cnt, r_is_virtual_doc
            """
        } else {
            if(path.endsWith('/')) path = path[0..-2]    // chop trailing slash
            dql =
            """
            select object_name, r_object_id, r_object_type, acl_name, acl_domain, r_content_size, a_content_type, r_link_cnt, r_is_virtual_doc
            from dm_sysobject
            where folder('$path')
            order by object_name, r_object_id, r_object_type, acl_name, acl_domain, r_content_size, a_content_type, r_link_cnt, r_is_virtual_doc
            """
        }

        // get list of map results via dql query
        // we're not getting persistnt results back - this ensures that
        // the IDfCollection is closed off asap and will hopefully speed things up
        def results = toList(session: session, dql: dql, queryType: queryType) { row ->
            row.folderPath = path
            row.objectPath = "$path/$row.object_name"
        }

        if(results) {
            for(attribs in results) {
                // identify folder-type containers with child elements
                // that are not virtual docs
                if(attribs.r_link_cnt > 0 && !attribs.r_is_virtual_doc) {
                    // walk by depth
                    if(!walkByWidth && recursive) {
                        walk([session: session, path: attribs.objectPath, queryType: queryType, walkByWidth: walkByWidth, recursive: recursive], closure)
                    }
                }

                // handle callback
                if(closure) { closure(attribs) }

                // identify folder-type containers with child elements
                // that are not virtual docs
                if(attribs.r_link_cnt > 0 && !attribs.r_is_virtual_doc) {
                    // walk by width
                    if(walkByWidth && recursive) {
                        walk([session: session, path: attribs.objectPath, queryType: queryType, walkByWidth: walkByWidth, recursive: recursive], closure)
                    }
                }
            }
        }
    }
}